﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

namespace Xtreme_Server
{
    public class FTPUserConnection
    {
        

        private class DataConnectionOperation
        {
            public Func<NetworkStream, string, string> Operation { get; set; }
            public string Arguments { get; set; }
        }

        //ILog _log = LogManager.GetLogger(typeof(FTPUserConnection));

        #region Copy Stream Implementations

        private static long CopyStream(Stream input, Stream output, int bufferSize)
        {
            byte[] buffer = new byte[bufferSize];
            int count = 0;
            long total = 0;

            while ((count = input.Read(buffer, 0, buffer.Length)) > 0)
            {
                output.Write(buffer, 0, count);
                total += count;
            }

            return total;
        }

        private static long CopyStreamAscii(Stream input, Stream output, int bufferSize)
        {
            char[] buffer = new char[bufferSize];
            int count = 0;
            long total = 0;

            using (StreamReader rdr = new StreamReader(input, Encoding.ASCII))
            {
                using (StreamWriter wtr = new StreamWriter(output, Encoding.ASCII))
                {
                    while ((count = rdr.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        wtr.Write(buffer, 0, count);
                        total += count;
                    }
                }
            }

            return total;
        }

        private long CopyStream(Stream input, Stream output)
        {
            Stream limitedStream = output; // new RateLimitingStream(output, 131072, 0.5);

            if (_connectionType == TransferType.Image)
            {
                return CopyStream(input, limitedStream, 4096);
            }
            else
            {
                return CopyStreamAscii(input, limitedStream, 4096);
            }
        }

        #endregion

        #region Enums

        private enum TransferType
        {
            Ascii,
            Ebcdic,
            Image,
            Local,
        }

        private enum FormatControlType
        {
            NonPrint,
            Telnet,
            CarriageControl,
        }

        private enum DataConnectionType
        {
            Passive,
            Active,
        }

        private enum FileStructureType
        {
            File,
            Record,
            Page,
        }

        #endregion

        private bool _disposed = false;

        private TcpListener _passiveListener;

        private TcpClient _controlClient;
        private TcpClient _dataClient;

        //private NetworkStream _dataStream;
        //private StreamReader _dataReader;
        //private StreamWriter _dataWriter;

        private NetworkStream _controlStream;
        private StreamReader _controlReader;
        private StreamWriter _controlWriter;

        private TransferType _connectionType = TransferType.Ascii;
        private FormatControlType _formatControlType = FormatControlType.NonPrint;
        private DataConnectionType _dataConnectionType = DataConnectionType.Active;
        private FileStructureType _fileStructureType = FileStructureType.File;

        private string _username;
        private string _root;
        private string _currentDirectory;
        private IPEndPoint _dataEndpoint;
        private IPEndPoint _remoteEndPoint;

        private X509Certificate _cert = null;
        private SslStream _sslStream;

        private string _clientIP;

        private FTPUser _currentUser;

        private List<string> _validCommands;



        //private string _username;

        public FTPUserConnection(TcpClient client)
        {
            _controlClient = client;

            _controlStream = _controlClient.GetStream();

            _controlReader = new StreamReader(_controlStream);
            _controlWriter = new StreamWriter(_controlStream);

            //_dataStream = _dataClient.GetStream();

            //_dataReader = new StreamReader(_dataStream);
            //_dataWriter = new StreamWriter(_dataStream);

            _root = Program.FTPRoot;
        }

        public void HandleClient(object obj)
        {
            if (Program.FTPWelcomeMsg != "")
            {
                _controlWriter.WriteLine("220 " + Program.FTPWelcomeMsg);
            }
            _controlWriter.WriteLine("220 Ready.");
            _controlWriter.Flush();

            string line;

            if(_dataEndpoint != null)
                _dataClient = new TcpClient(_dataEndpoint.AddressFamily);

            string renameFrom = null;

            try
            {
                while (!string.IsNullOrEmpty(line = _controlReader.ReadLine()))
                {
                    string response = null;

                    string[] command = line.Split(' ');

                    string cmd = command[0].ToUpperInvariant();
                    string arguments = command.Length > 1 ? line.Substring(command[0].Length + 1) : null;

                    if (string.IsNullOrWhiteSpace(arguments))
                        arguments = null;

                    if (cmd != "RNTO")
                    {
                        renameFrom = null;
                    }

                    if (response == null)
                    {
                        switch (cmd)
                        {
                            case "USER":
                                response = User(arguments);
                                break;
                            case "PASS":
                                response = Password(arguments);
                                break;
                            case "CWD":
                                response = ChangeWorkingDirectory(arguments);
                                break;
                            case "CDUP":
                                response = ChangeWorkingDirectory("..");
                                break;
                            case "REIN":
                                _currentUser = null;
                                _username = null;
                                _passiveListener = null;
                                _dataClient = null;

                                response = "220 Service ready for new user.";
                                break;
                            case "STRU":
                                response = Structure(arguments);
                                break;
                            case "MODE":
                                response = Mode(arguments);
                                break;
                            case "RNFR":
                                string a7 = arguments.Replace('/', '\\');
                                if (a7[0] == '\\') a7 = a7.Substring(1);
                                string b7 = Path.Combine(Program.FTPRoot, a7);
                                renameFrom = b7;
                                response = "350 Requested file action pending further information";
                                break;
                            case "RNTO":
                                string a1 = arguments.Replace('/', '\\');
                                if (a1[0] == '\\') a1 = a1.Substring(1);
                                string b1 = Path.Combine(Program.FTPRoot, a1);
                                response = Rename(renameFrom, b1);
                                break;
                            case "DELE":
                                string a2 = arguments.Replace('/', '\\');
                                if (a2[0] == '\\') a2 = a2.Substring(1);
                                string b2 = Path.Combine(Program.FTPRoot, a2);
                                response = Delete(b2);
                                break;
                            case "RMD":
                                string a3 = arguments.Replace('/', '\\');
                                if (a3[0] == '\\') a3 = a3.Substring(1);
                                string b3 = Path.Combine(Program.FTPRoot, a3);
                                response = RemoveDir(b3);
                                break;
                            case "MKD":
                                string a4 = arguments.Replace('/', '\\');
                                if (a4[0] == '\\') a4 = a4.Substring(1);
                                string b4 = Path.Combine(Program.FTPRoot, a4);
                                response = CreateDir(b4);
                                break;
                            case "PWD":
                                if (_currentDirectory != null)
                                {
                                    string x1 = _currentDirectory.Replace(Program.FTPRoot, "").Replace('\\', '/');
                                    if (string.IsNullOrEmpty(x1) || string.IsNullOrWhiteSpace(x1))
                                    {
                                        x1 = "/";
                                    }
                                    response = String.Format("257 \"{0}\" is the current directory.", x1);
                                }
                                else
                                {
                                    response = String.Format("257 \"{0}\" is the current directory.", "/");
                                }

                                break;
                            case "STOR":
                                string a5 = arguments.Replace('/', '\\');
                                if (a5[0] == '\\') a5 = a5.Substring(1);
                                string b5 = Path.Combine(Program.FTPRoot, a5);
                                response = Store(b5);
                                break;
                            case "STOU":
                                response = StoreUnique();
                                break;
                            case "APPE":
                                string a6 = arguments.Replace('/', '\\');
                                if (a6[0] == '\\') a6 = a6.Substring(1);
                                string b6 = Path.Combine(Program.FTPRoot, a6);
                                response = Append(b6);
                                break;
                            case "SYST":
                                response = "215 UNIX Type: L8";
                                break;
                            case "NOOP":
                                response = "200 OK";
                                break;
                            case "ACCT":
                                response = "200 OK";
                                break;
                            case "ALLO":
                                response = "200 OK";
                                break;
                            case "NLST":
                                response = "502 Command not implemented";
                                break;
                            case "SITE":
                                response = "502 Command not implemented";
                                break;
                            case "STAT":
                                response = "502 Command not implemented";
                                break;
                            case "HELP":
                                response = "502 Command not implemented";
                                break;
                            case "SMNT":
                                response = "502 Command not implemented";
                                break;
                            case "REST":
                                response = "502 Command not implemented";
                                break;
                            case "ABOR":
                                response = "502 Command not implemented";
                                break;
                            case "QUIT":
                                response = "221 Closing control connection";
                                break;
                            case "TYPE":
                                string[] splitArgs = arguments.Split(' ');
                                response = Type(splitArgs[0], splitArgs.Length > 1 ? splitArgs[1] : null);
                                break;
                            case "PORT":
                                response = Port(arguments);
                                break;
                            case "PASV":
                                response = Passive();
                                break;
                            case "LIST":
                                response = List(_currentDirectory);
                                break;
                            case "RETR":
                                string a = arguments.Replace('/', '\\');
                                if (a[0] == '\\') a = a.Substring(1);
                                string b = Path.Combine(this._currentDirectory, a);
                                response = Retrieve(b);
                                break;



                            // Extensions defined by rfc 2228
                            case "AUTH":
                                response = Auth(arguments);
                                break;

                            // Extensions defined by rfc 2389
                            case "FEAT":
                                response = FeatureList();
                                break;
                            case "OPTS":
                                response = Options(arguments);
                                break;

                            // Extensions defined by rfc 3659
                            case "MDTM":
                                response = FileModificationTime(arguments);
                                break;
                            case "SIZE":
                                response = FileSize(arguments);
                                break;

                            // Extensions defined by rfc 2428
                            case "EPRT":
                                response = EPort(arguments);
                                break;
                            case "EPSV":
                                response = EPassive();
                                break;



                            default:
                                response = "502 Command not implemented";
                                break;
                        }
                    }

                    if (_controlClient == null || !_controlClient.Connected)
                    {
                        break;
                    }
                    else
                    {
                        _controlWriter.WriteLine(response);
                        _controlWriter.Flush();

                        if (response.StartsWith("221"))
                        {
                            break;
                        }

                        if (cmd == "AUTH")
                        {
                            _cert = new X509Certificate("xtreme.cer");

                            _sslStream = new SslStream(_controlStream);

                            _sslStream.AuthenticateAsServer(_cert);

                            _controlReader = new StreamReader(_sslStream);
                            _controlWriter = new StreamWriter(_sslStream);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                throw;
            }
        }

        #region FTP Commands

        private string Store(string pathname)
        {
            pathname = NormalizeFilename(pathname);

            if (pathname != null)
            {
                var state = new DataConnectionOperation { Arguments = pathname, Operation = StoreOperation };

                SetupDataConnectionOperation(state);

                return string.Format("150 Opening {0} mode data transfer for STOR", _dataConnectionType);
            }

            return "450 Requested file action not taken";
        }

        private string Append(string pathname)
        {
            pathname = NormalizeFilename(pathname);

            if (pathname != null)
            {
                var state = new DataConnectionOperation { Arguments = pathname, Operation = AppendOperation };

                SetupDataConnectionOperation(state);

                return string.Format("150 Opening {0} mode data transfer for APPE", _dataConnectionType);
            }

            return "450 Requested file action not taken";
        }

        private string StoreUnique()
        {
            string pathname = NormalizeFilename(new Guid().ToString());

            var state = new DataConnectionOperation { Arguments = pathname, Operation = StoreOperation };

            SetupDataConnectionOperation(state);

            return string.Format("150 Opening {0} mode data transfer for STOU", _dataConnectionType);
        }

        private string Delete(string pathname)
        {
            pathname = NormalizeFilename(pathname);

            if (pathname != null)
            {
                if (File.Exists(pathname))
                {
                    File.Delete(pathname);
                }
                else
                {
                    return "550 File Not Found";
                }

                return "250 Requested file action okay, completed";
            }

            return "550 File Not Found";
        }

        private string RemoveDir(string pathname)
        {
            pathname = NormalizeFilename(pathname);

            if (pathname != null)
            {
                if (Directory.Exists(pathname))
                {
                    Directory.Delete(pathname);
                }
                else
                {
                    return "550 Directory Not Found";
                }

                return "250 Requested file action okay, completed";
            }

            return "550 Directory Not Found";
        }

        private string CreateDir(string pathname)
        {
            pathname = NormalizeFilename(pathname);

            if (pathname != null)
            {
                if (!Directory.Exists(pathname))
                {
                    Directory.CreateDirectory(pathname);
                }
                else
                {
                    return "550 Directory already exists";
                }

                return "250 Requested file action okay, completed";
            }

            return "550 Directory Not Found";
        }

        private string Structure(string structure)
        {
            switch (structure)
            {
                case "F":
                    _fileStructureType = FileStructureType.File;
                    break;
                case "R":
                case "P":
                    return string.Format("504 STRU not implemented for \"{0}\"", structure);
                default:
                    return string.Format("501 Parameter {0} not recognized", structure);
            }

            return "200 Command OK";
        }

        private string Mode(string mode)
        {
            if (mode.ToUpperInvariant() == "S")
            {
                return "200 OK";
            }
            else
            {
                return "504 Command not implemented for that parameter";
            }
        }

        private string Rename(string renameFrom, string renameTo)
        {
            if (string.IsNullOrWhiteSpace(renameFrom) || string.IsNullOrWhiteSpace(renameTo))
            {
                return "450 Requested file action not taken";
            }

            renameFrom = NormalizeFilename(renameFrom);
            renameTo = NormalizeFilename(renameTo);

            if (renameFrom != null && renameTo != null)
            {
                if (File.Exists(renameFrom))
                {
                    File.Move(renameFrom, renameTo);
                }
                else if (Directory.Exists(renameFrom))
                {
                    Directory.Move(renameFrom, renameTo);
                }
                else
                {
                    return "450 Requested file action not taken";
                }

                return "250 Requested file action okay, completed";
            }

            return "450 Requested file action not taken";
        }

        private string EPassive()
        {
            _dataConnectionType = DataConnectionType.Passive;

            IPAddress localIp = ((IPEndPoint)_controlClient.Client.LocalEndPoint).Address;

            _passiveListener = new TcpListener(localIp, 0);
            _passiveListener.Start();

            IPEndPoint passiveListenerEndpoint = (IPEndPoint)_passiveListener.LocalEndpoint;

            return string.Format("229 Entering Extended Passive Mode (|||{0}|)", passiveListenerEndpoint.Port);
        }

        private string EPort(string hostPort)
        {
            _dataConnectionType = DataConnectionType.Active;

            char delimiter = hostPort[0];

            string[] rawSplit = hostPort.Split(new char[] { delimiter }, StringSplitOptions.RemoveEmptyEntries);

            char ipType = rawSplit[0][0];

            string ipAddress = rawSplit[1];
            string port = rawSplit[2];

            _dataEndpoint = new IPEndPoint(IPAddress.Parse(ipAddress), int.Parse(port));

            return "200 Data Connection Established";
        }


        private string Options(string arguments)
        {
            return "200 Dunno ^^";
        }

        private string FeatureList()
        {
            _controlWriter.WriteLine("211 Extensions supported:");
            _controlWriter.WriteLine(" MDTM");
            _controlWriter.WriteLine(" SIZE");
            return "211 End";
        }



        private string FileModificationTime(string pathname)
        {
            pathname = NormalizeFilename(pathname);

            if (pathname != null)
            {
                if (File.Exists(pathname))
                {
                    return string.Format("213 {0}", File.GetLastWriteTime(pathname).ToString("yyyyMMddHHmmss.fff"));
                }
            }

            return "550 File not found";
        }

        private string FileSize(string pathname)
        {
            pathname = NormalizeFilename(pathname);

            if (pathname != null)
            {
                if (File.Exists(pathname))
                {
                    long length = 0;

                    using (FileStream fs = File.Open(pathname, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        length = fs.Length;
                    }

                    return string.Format("213 {0}", length);
                }
            }

            return "550 File not found";
        }







        private string Auth(string authMode)
        {
            if (authMode == "TLS")
            {
                return "234 Enabling TLS Connection";
            }
            else
            {
                return "504 Unrecognized AUTH mode";
            }
        }


        private string Retrieve(string pathname)
        {
            pathname = NormalizeFilename(pathname);

            if (pathname != null)
            {
                if (File.Exists(pathname))
                {
                    var state = new DataConnectionOperation { Arguments = pathname, Operation = RetrieveOperation };

                    SetupDataConnectionOperation(state);

                    return string.Format("150 Opening {0} mode data transfer for RETR", _dataConnectionType);
                }
            }

            return "550 File Not Found";
        }

        private string User(string username)
        {
            _username = username;

            return "331 Username OK, waiting for password";
        }

        private string Password(string password)
        {
            if (Program.ftp.Users.Where(usr => usr.Name == _username).Count() != 0)
            {
                return "230 User logged in";
            }
            else
            {
                return "530 Incorrect password";
            }
        }

        private string CurrentDirectory;

        /*private string ChangeWorkingDirectory(string pathname)
        {
            if (CurrentDirectory == null)
            {
                CurrentDirectory = "/";
            }

            if(pathname.StartsWith("/"))
            {
                pathname.Remove(pathname.IndexOf('/'), 1);
            }

            if (pathname == "..")
            {
                if (CurrentDirectory == "/")
                {
                }
                else
                {
                    if (CurrentDirectory.IndexOf('/') != CurrentDirectory.LastIndexOf('/'))
                    {
                        if(CurrentDirectory.EndsWith("/"))
                        {
                            CurrentDirectory.Remove(CurrentDirectory.LastIndexOf('/'));
                            CurrentDirectory.Remove(CurrentDirectory.LastIndexOf('/'));
                        }
                    }
                }
            }
            else
            {
                //if (!pathname.Contains('/'))
                //{
                    
                    if (CurrentDirectory.EndsWith("/"))
                    {
                        CurrentDirectory += pathname;
                    }
                    else
                    {
                        CurrentDirectory += '/';
                        CurrentDirectory += pathname;
                    }
                //}
            }
            //CurrentDirectory = pathname;
            return "250 Changed to new directory";
        }*/

        private string ChangeWorkingDirectory(string pathname)
        {
            if (pathname == "/")
            {
                _currentDirectory = _root;
            }
            else
            {
                string newDir;

                if (pathname.StartsWith("/"))
                {
                    pathname = pathname.Substring(1).Replace('/', '\\');
                    newDir = Path.Combine(_root, pathname);
                }
                else
                {
                    pathname = pathname.Replace('/', '\\');
                    newDir = Path.Combine(_currentDirectory, pathname);
                }

                if (Directory.Exists(newDir))
                {
                    _currentDirectory = new DirectoryInfo(newDir).FullName;

                    if (!IsPathValid(_currentDirectory))
                    {
                        _currentDirectory = _root;
                    }
                }
                else
                {
                    _currentDirectory = _root;
                }
            }

            return "250 Changed to new directory";
        }

        private string GetRealDir(string pathname)
        {
            string s;
            if (pathname == "/")
            {
                s = _root;
            }
            else
            {
                string newDir;

                if (pathname.StartsWith("/"))
                {
                    pathname = pathname.Substring(1).Replace('/', '\\');
                    newDir = Path.Combine(_root, pathname);
                }
                else
                {
                    pathname = pathname.Replace('/', '\\');
                    newDir = Path.Combine(_currentDirectory, pathname);
                }

                if (Directory.Exists(newDir))
                {
                    s = new DirectoryInfo(newDir).FullName;

                    if (!IsPathValid(s))
                    {
                        s = _root;
                    }
                }
                else
                {
                    s = _root;
                }
            }
            return s;
        }

        string _transferType;

        private string Port(string hostPort)
        {
            _dataConnectionType = DataConnectionType.Active;

            string[] ipAndPort = hostPort.Split(',');

            byte[] ipAddress = new byte[4];
            byte[] port = new byte[2];

            for (int i = 0; i < 4; i++)
            {
                ipAddress[i] = Convert.ToByte(ipAndPort[i]);
            }

            for (int i = 4; i < 6; i++)
            {
                port[i - 4] = Convert.ToByte(ipAndPort[i]);
            }

            if (BitConverter.IsLittleEndian)
                Array.Reverse(port);

            int a = 0;
            int b = port[0];
            int c = port[1];
            string d = b.ToString("X");
            string e = c.ToString("X");
            string f = d + e;
            int g = int.Parse(f, System.Globalization.NumberStyles.HexNumber);

            _dataEndpoint = new IPEndPoint(new IPAddress(ipAddress), /*BitConverter.ToInt16(port, 0)*/ g);

            return "200 Data Connection Established";
        }

        private string Type(string typeCode, string formatControl)
        {
            switch (typeCode.ToUpperInvariant())
            {
                case "A":
                    _connectionType = TransferType.Ascii;
                    break;
                case "I":
                    _connectionType = TransferType.Image;
                    break;
                default:
                    return "504 Command not implemented for that parameter";
            }

            if (!string.IsNullOrWhiteSpace(formatControl))
            {
                switch (formatControl.ToUpperInvariant())
                {
                    case "N":
                        _formatControlType = FormatControlType.NonPrint;
                        break;
                    default:
                        return "504 Command not implemented for that parameter";
                }
            }

            return string.Format("200 Type set to {0}", _connectionType);
        }

      

        //private DataConnectionType _dataConnectionType = DataConnectionType.Active;

        public string Passive()
        {
            _dataConnectionType = DataConnectionType.Passive;

            IPAddress localIp = ((IPEndPoint)_controlClient.Client.LocalEndPoint).Address;

            _passiveListener = new TcpListener(localIp, 0);
            _passiveListener.Start();

            IPEndPoint passiveListenerEndpoint = (IPEndPoint)_passiveListener.LocalEndpoint;

            byte[] address = passiveListenerEndpoint.Address.GetAddressBytes();
            short port = (short)passiveListenerEndpoint.Port;

            byte[] portArray = BitConverter.GetBytes(port);

            if (BitConverter.IsLittleEndian)
                Array.Reverse(portArray);

            return string.Format("227 Entering Passive Mode ({0},{1},{2},{3},{4},{5})", address[0], address[1], address[2], address[3], portArray[0], portArray[1]);
        }

        private string List(string pathname)
        {
            //pathname = NormalizeFilename(pathname);

            //if (pathname != null)
            //{
                var state = new DataConnectionOperation { Arguments = pathname, Operation = ListOperation };

                SetupDataConnectionOperation(state);

                return string.Format("150 Opening {0} mode data transfer for LIST", _dataConnectionType);
            //}

            //return "450 Requested file action not taken";
        }



        private void DoList(IAsyncResult result)
        {
            if (_dataConnectionType == DataConnectionType.Active)
            {
                _dataClient.EndConnect(result);
            }
            else
            {
                _dataClient = _passiveListener.EndAcceptTcpClient(result);
            }

            string pathname = (string)result.AsyncState;

            using (NetworkStream dataStream = _dataClient.GetStream())
            {
                StreamReader _dataReader = new StreamReader(dataStream, Encoding.ASCII);
                StreamWriter _dataWriter = new StreamWriter(dataStream, Encoding.ASCII);
                IEnumerable<string> directories = Directory.EnumerateDirectories(pathname);

                foreach (string dir in directories)
                {
                    DirectoryInfo d = new DirectoryInfo(dir);

                    string date = d.LastWriteTime < DateTime.Now - TimeSpan.FromDays(180) ?
                        d.LastWriteTime.ToString("MMM dd  yyyy") :
                        d.LastWriteTime.ToString("MMM dd HH:mm");

                    string line = string.Format("drwxr-xr-x    2 {3}     {4}     {0,8} {1} {2}", "4096", date, d.Name, "", "");

                    _dataWriter.WriteLine(line);
                    _dataWriter.Flush();
                }
                IEnumerable<string> files = Directory.EnumerateFiles(pathname);

                foreach (string file in files)
                {
                    FileInfo f = new FileInfo(file);

                    string date = f.LastWriteTime < DateTime.Now - TimeSpan.FromDays(180) ?
                        f.LastWriteTime.ToString("MMM dd  yyyy") :
                        f.LastWriteTime.ToString("MMM dd HH:mm");

                    string line = string.Format("-rw-r--r--    2 {3}     {4}     {0,8} {1} {2}", f.Length, date, f.Name, System.IO.File.GetAccessControl(pathname).GetOwner(typeof(System.Security.Principal.NTAccount)).ToString());

                    _dataWriter.WriteLine(line);
                    _dataWriter.Flush();
                }
                _dataClient.Close();
                _dataClient = null;

                _controlWriter.WriteLine("226 Transfer complete");
                _controlWriter.Flush();
            }
        }








        private string NormalizeFilename(string path)
        {
            if (path == null)
            {
                path = string.Empty;
            }

            if (path == "/")
            {
                return _root;
            }
            else if (path.StartsWith("/"))
            {
                path = new FileInfo(Path.Combine(_root, path.Substring(1))).FullName;
            }
            else
            {
                path = new FileInfo(Path.Combine(_currentDirectory, path)).FullName;
            }

            return IsPathValid(path) ? path : null;
        }

        private bool IsPathValid(string path)
        {
            return path.StartsWith(_root);
        }
        #endregion

        #region DataConnection Operations
        private void HandleAsyncResult(IAsyncResult result)
        {
            //if (_dataConnectionType == DataConnectionType.Active)
            //{
            //    _dataClient.EndConnect(result);
            //}
            //else
            //{
            /*try
            {*/
            _dataClient = _passiveListener.EndAcceptTcpClient(result);
            /*}
            catch (ArgumentException e)
            {
                _dataClient = new TcpClient() { Client = _passiveListener.EndAcceptSocket(result) };
            }*/


            //_dataClient = new TcpClient(_dataEndpoint);
            /*_dataClient = new TcpClient(_dataEndpoint.AddressFamily);
            _dataClient.BeginConnect(_dataEndpoint.Address, _dataEndpoint.Port, DoDataConnectionOperation, DataConnectionType.Active);*/
            //}
        }

        private void SetupDataConnectionOperation(DataConnectionOperation state)
        {
            if (_dataConnectionType == DataConnectionType.Active)
            {
                _dataClient = new TcpClient(_dataEndpoint.AddressFamily);
                _dataClient.BeginConnect(_dataEndpoint.Address, _dataEndpoint.Port, DoDataConnectionOperation, state);
            }
            else
            {
                _passiveListener.BeginAcceptTcpClient(DoDataConnectionOperation, state);
            }
        }

        private void DoDataConnectionOperation(IAsyncResult result)
        {
            HandleAsyncResult(result);

            DataConnectionOperation op = result.AsyncState as DataConnectionOperation;

            string response;

            using (NetworkStream dataStream = _dataClient.GetStream())
            {
                response = op.Operation(dataStream, op.Arguments);
            }

            _dataClient.Close();
            _dataClient = null;

            _controlWriter.WriteLine(response);
            _controlWriter.Flush();
        }

        private string RetrieveOperation(NetworkStream dataStream, string pathname)
        {
            long bytes = 0;

            using (FileStream fs = new FileStream(pathname, FileMode.Open, FileAccess.Read))
            {
                bytes = CopyStream(fs, dataStream);
            }

            return "226 Closing data connection, file transfer successful";
        }

        private string StoreOperation(NetworkStream dataStream, string pathname)
        {
            long bytes = 0;

            using (FileStream fs = new FileStream(pathname, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 4096, FileOptions.SequentialScan))
            {
                bytes = CopyStream(dataStream, fs);
            }

          

            return "226 Closing data connection, file transfer successful";
        }

        private string AppendOperation(NetworkStream dataStream, string pathname)
        {
            long bytes = 0;

            using (FileStream fs = new FileStream(pathname, FileMode.Append, FileAccess.Write, FileShare.None, 4096, FileOptions.SequentialScan))
            {
                bytes = CopyStream(dataStream, fs);
            }

            

            return "226 Closing data connection, file transfer successful";
        }



        private string GetFilePermissions(string pathname)
        {
            return "";
        }

        enum UnixPerm
        {
            Read = 4,
            Write = 2,
            Run = 1
        }

        public class xPerm
        {
            public bool CanRead = false;
            public bool CanWrite = false;
            public bool CanRun = false;
        }

        xPerm getUnixPerm(int perm)
        {
            xPerm r = new xPerm();
            switch (perm)
            {
                case 0:
                    break;
                case 1:
                    r.CanRun = true;
                    break;
                case 2:
                    r.CanWrite = true;
                    break;
                case 3:
                    r.CanRun = true;
                    r.CanWrite = true;
                    break;
                case 4:
                    r.CanRead = true;
                    break;
                case 5:
                    r.CanRun = true;
                    r.CanRead = true;
                    break;
                case 6:
                    r.CanRead = true;
                    r.CanWrite = true;
                    break;
                case 7:
                    r.CanRun = true;
                    r.CanRead = true;
                    r.CanWrite = true;
                    break;
                default:
                    break;
            }
            return r;
        }

        private void SetFilePermissions(int perm, string path)
        {
            string a = perm.ToString();
            xPerm owner = getUnixPerm(a[0]);
            xPerm group = getUnixPerm(a[1]);
            xPerm other = getUnixPerm(a[2]);



            FileAttributes attributes = File.GetAttributes(path);

            if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
            {
                // Show the file.
                attributes = RemoveAttribute(attributes, FileAttributes.Hidden);
                File.SetAttributes(path, attributes);
                Console.WriteLine("The {0} file is no longer hidden.", path);
            }
            else
            {
                // Hide the file.
                File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.Hidden);
                Console.WriteLine("The {0} file is now hidden.", path);
            }
        }

        private static FileAttributes RemoveAttribute(FileAttributes attributes, FileAttributes attributesToRemove)
        {
            return attributes & ~attributesToRemove;
        }





        private string ListOperation(NetworkStream dataStream, string pathname)
        {
            if (_currentDirectory == null)
                _currentDirectory = "/";

            pathname = _currentDirectory;
            pathname = GetRealDir(pathname);

            //pathname = pathname.Replace('/', '\\');
            //pathname = pathname.Remove(0, 1);
            //if (pathname.StartsWith("\\"))
            //{
            //    pathname = pathname.Remove(0, 1);
            //}

            //string[] gar = pathname.Split('/');

            //pathname = Program.FTPRoot;

            /*foreach (string s in gar)
            {
                if (pathname.EndsWith("\\"))
                {
                    pathname += s;
                }
                else
                {
                    pathname += '\\';
                    pathname += s;
                }
            }*/

            //pathname = Path.Combine(Program.FTPRoot, pathname);

            StreamWriter dataWriter = new StreamWriter(dataStream, Encoding.ASCII);

            IEnumerable<string> directories = Directory.EnumerateDirectories(pathname);

            foreach (string dir in directories)
            {
                DirectoryInfo d = new DirectoryInfo(dir);

                string date = d.LastWriteTime < DateTime.Now - TimeSpan.FromDays(180) ?
                    d.LastWriteTime.ToString("MMM dd  yyyy") :
                    d.LastWriteTime.ToString("MMM dd HH:mm");

                if (d.Name == "ACER")
                {
                }


                string line = string.Format("drwxr-xr-x    2 {3}     {4}     {0,8} {1} {2}", "4096", date, d.Name, "", "");

                dataWriter.WriteLine(line);
                dataWriter.Flush();
            }

            IEnumerable<string> files = Directory.EnumerateFiles(pathname);

            foreach (string file in files)
            {
                FileInfo f = new FileInfo(file);

                string date = f.LastWriteTime < DateTime.Now - TimeSpan.FromDays(180) ?
                    f.LastWriteTime.ToString("MMM dd  yyyy") :
                    f.LastWriteTime.ToString("MMM dd HH:mm");

                string line = string.Format("-rw-r--r--    2 {3}     {4}     {0,8} {1} {2}", f.Length, date, f.Name, System.IO.File.GetAccessControl(pathname).GetOwner(typeof(System.Security.Principal.NTAccount)).ToString(), "");

                dataWriter.WriteLine(line);
                dataWriter.Flush();
            }

          

            return "226 Transfer complete";
        }
        #endregion
    }
}
